﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Web;
using System.Web.Mvc;
using Fuse8.ContentManagementFramework.BizFacade.Services.Contents;
using Fuse8.ContentManagementFramework.BizFacade.Services.Contents.Queryable;
using Fuse8.ContentManagementFramework.Domain;
using Fuse8.ContentManagementFramework.Web.ApplicationRuntime.ContentModuleModels;

namespace Fuse8.ContentManagementFramework.Web.MvcPowered.Components.Contents
{
    public sealed class CmsContentBuilderComponent : CmsMvcBaseComponent<CmsContentBuilderComponent, GenericCmsEntity>
    {
        private readonly int contentTypeId;
        private readonly HtmlHelper helper;
        private Expression<Func<GenericCmsEntity, bool>> filter;
        private List<JoinModel> joins;
        private PagerModel pager;
        private List<Tuple<string, bool>> orders = new List<Tuple<string, bool>>();
        private string mainSelect;
        private Func<PagerViewModel, object> pagerTemplate;
        
        private CmsPagerPosition pagerPosition;
        private PagerViewModel pagerViewModel;
        private string noResultMessage;

        public CmsContentBuilderComponent(int contentTypeId, HtmlHelper helper) : base(helper)
        {
            this.contentTypeId = contentTypeId;
            this.helper = helper;
            joins = new List<JoinModel>();
            noResultMessage = "No items were found";
            Component = this;
            Value = new GenericCmsEntity();
        }

        public CmsContentBuilderComponent Select(params string[] selects)
        {
            mainSelect = string.Join(",", selects);
            return this;
        }

        public CmsContentBuilderComponent Where(Expression<Func<GenericCmsEntity, bool>> value)
        {
            filter = value;
            return this;
        }

        public CmsContentBuilderComponent Join(int joinContentTypeId, string primaryKey, string foreignKey, params string[] selects)
        {
            joins.Add(new JoinModel()
                          {
                              TypeId = joinContentTypeId,
                              ForeignKey = foreignKey,
                              PrimaryKey = primaryKey,
                              Select = string.Join(",", selects)
                          });
            return this;
        }

        public CmsContentBuilderComponent Pager(int pageSize, int visibleLinksCount, string key, CmsPagerPosition position = CmsPagerPosition.Top)
        {
            pager = new PagerModel()
                        {
                            PageKey = key,
                            PageSize = pageSize,
                            VisibleLinkCount = visibleLinksCount
                        };
            pagerPosition = position;
            return this;
        }

        public CmsContentBuilderComponent OrderBy(string field, bool desc = false)
        {
            orders.Add(new Tuple<string, bool>(field, desc));
            return this;
        }

        public CmsContentBuilderComponent PagerTemplate(Func<PagerViewModel, object> value)
        {
            pagerTemplate = value;
            return this;
        }

        public CmsContentBuilderComponent NoResultMessage(string value)
        {
            noResultMessage = value;
            return this;
        }

        protected override string RenderTemplate()
        {
            EnsureRequireSettings();

            IQueryable<GenericCmsEntity> list = filter == null
                                      ? GenericContentService.GetAll(contentTypeId)
                                      : GenericContentService.GetAll(contentTypeId).Where(filter);

            list = SetJoins(list);
            list = SetOrder(list);
            list = SetPager(list);

            if (!string.IsNullOrEmpty(mainSelect))
            {
                list = list.SelectProperties(mainSelect.Split(','));
            }

            return Render(list);
        }

        private string Render(IQueryable<GenericCmsEntity> list)
        {
            var sb = new StringBuilder();
            var showPager = pagerViewModel != null && pagerViewModel.ShowPager;

            if (showPager && (pagerPosition == CmsPagerPosition.Top || pagerPosition == CmsPagerPosition.TopAndBottom))
            {
                sb.Append(pagerTemplate.Invoke(pagerViewModel).ToString());
            }

            var items = list.ToList();
            if (items.Count > 0)
            {
                foreach (var item in items)
                {
                    sb.Append(InlineTemplate.Invoke(item).ToString());
                }
            }
            else
            {
                sb.Append(noResultMessage);
            }

            if (showPager && (pagerPosition == CmsPagerPosition.Bottom || pagerPosition == CmsPagerPosition.TopAndBottom))
            {
                sb.Append(pagerTemplate.Invoke(pagerViewModel).ToString());
            }

            return sb.ToString();
        }

        private IQueryable<GenericCmsEntity> SetPager(IQueryable<GenericCmsEntity> list)
        {
            //todo: this code is copy-pasted from CmsContentModuleCOntroller. Must be refactored
            if (pager == null)
            {
                return list;
            }
            var request = helper.ViewContext.RequestContext.HttpContext.Request;
            int page = Convert.ToInt32(request.QueryString[pager.PageKey] ?? "1");
            var total = list.Count();
            int pageSize = pager.PageSize;
            list = list.Skip(pager.PageSize * (page - 1)).Take(pager.PageSize);
            pagerViewModel = new PagerViewModel
            {
                AfterCurrentPages = new List<PagerItemModel>(),
                BeforeCurrentPages = new List<PagerItemModel>(),
                Key = pager.PageKey,
                Url = request.Url.AbsolutePath + "?",
                TotalCount = total
            };
            foreach (var key in request.QueryString.AllKeys)
            {
                if (key != pagerViewModel.Key)
                {
                    pagerViewModel.Url += string.Format("{0}={1}&", key, request.QueryString[key]);
                }
            }
            pagerViewModel.ShowPager = total > pageSize;
            var pageCount = (int)Math.Ceiling(total / (double)pageSize);
            int visibleLinksCount = pager.VisibleLinkCount;
            int currentGroup = page % visibleLinksCount == 0
                                   ? ((int)Math.Floor((double)page / visibleLinksCount)) - 1
                                   : (int)Math.Floor((double)page / visibleLinksCount);

            int start = currentGroup * visibleLinksCount + 1;
            int end = currentGroup * visibleLinksCount + visibleLinksCount;

            if (start > visibleLinksCount)
            {
                pagerViewModel.PrevLink = new PagerItemModel() { Index = start - 1 };
            }

            if (end < pageCount)
            {
                pagerViewModel.NextLink = new PagerItemModel() { Index = end + 1 };
            }
            else
            {
                end = pageCount;
            }

            for (int i = start; i <= end; i++)
            {
                if (i < page)
                {
                    pagerViewModel.BeforeCurrentPages.Add(new PagerItemModel() { Index = i });
                }
                if (i == page)
                {
                    pagerViewModel.CurrentPage = new PagerItemModel() { Index = i };
                }
                if (i > page)
                {
                    pagerViewModel.AfterCurrentPages.Add(new PagerItemModel() { Index = i });
                }
            }
            return list;
        }

        private IQueryable<GenericCmsEntity> SetOrder(IQueryable<GenericCmsEntity> list)
        {
            if (orders.Count > 0)
            {
                var order = orders.Last();
                var field = order.Item1;
                if (order.Item2)
                {
                    list = list.OrderByDescending(p => p[field]);
                }
                else
                {
                    list = list.OrderBy(p => p[field]);
                }
            }

            return list;
        }

        private IQueryable<GenericCmsEntity> SetJoins(IQueryable<GenericCmsEntity> list)
        {
            foreach (var joinModel in joins)
            {
                JoinModel model = joinModel;
                var selects = model.Selects;
                if (joinModel.TypeId.HasValue)
                {
                    list = list.JoinContent(joinModel.TypeId.Value, joinModel.PrimaryKey, joinModel.ForeignKey,
                                            () => selects);
                }
                else if (!string.IsNullOrEmpty(joinModel.Type))
                {
                    var type = Type.GetType(joinModel.Type, true, false);
                    list = list.JoinWith(joinModel.PrimaryKey, joinModel.ForeignKey, () => selects, type);
                }
            }

            return list;
        }

        private void EnsureRequireSettings()
        {
            if (InlineTemplate == null)
            {
                throw new ArgumentException("Template is not set");
            }
            if (pager != null && pagerTemplate == null)
            {
                throw new ArgumentException("Pager template is not set");
            }
        }
    }
}